#    Tichy
#
#    copyright 2008 Guillaume Chereau (charlie137@gmail.com)
#
#    This file is part of Tichy.
#
#    Tichy is free software: you can redistribute it and/or modify it
#    under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    Tichy is distributed in the hope that it will be useful, but
#    WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with Tichy.  If not, see <http://www.gnu.org/licenses/>.

from geo import Vect, Rect, asvect
from tichy.tasklet import Tasklet, Wait
from tichy.object import Object
from tichy.style import StyleSheet


class Widget(Object):
    """Base class for all the widgets

    This is really similar to gtk.widget, except lighter.
    """

    def __init__(self, parent, style=None, optimal_size=None,
                 expand=False, item=None, tags=[], pos=None, min_size=None,
                 **kargs):
        """Create a new Widget

        parameters:

        - parent The parent widget where we put this widget

        - style The style associated with this widget The style is not
          compulsory, it is only something that can be used by the
          Design

        - optimal_size The size requested for the widget

        - min_size The minimum size requested for the widget

        - expand If true the widget will try to take all the place it
          can

        - item What is the item associated with this widget (None if
          not) This is only used for the style rules

        - tags A list of string, Can be used by the style rules
        """
        super(Widget, self).__init__()
        self.children = []
        self.item = item    # Set to None if the object is not a view
                            # on an item
        parent = parent.get_contents_child() if parent else None
        assert isinstance(parent, Widget) or parent is None
        self.parent = parent

        self.tags = set(tags)

        self.__optimal_size = optimal_size or Vect(0, 0)
        self.__min_size = min_size
        self.expand = expand

        self._messed = False
        self._need_compute_sizes = False
        self._need_organize = False

        self.__destroyed = False

        self.rect = Rect((0, 0), min_size or Vect(0, 0))
        self.__pos = pos or Vect(0, 0)

        self.style_sheet = style

        self.focused = None
        self.clickable = False
        self.surface = None     # This is used for the widget that
                                # keep a copy of there surface for
                                # optimisation
        self.store_surface = False   # Set to true for the widget to
                                     # keep a memory of it own surface

        if parent:
            parent.add(self)

    def __get_style_sheet(self):
        return self.__style_sheet

    def __set_style_sheet(self, stylesheet):
        if stylesheet and self.parent:
            self.__style_sheet = self.parent.style_sheet.merge(stylesheet)
        elif stylesheet and not self.parent:
            self.__style_sheet = stylesheet
        elif self.parent:
            self.__style_sheet = self.parent.style_sheet
        else:
            self.__style_sheet = StyleSheet()
        self.style = self.__style_sheet.apply(self)

        for child in self.children:
            child.style_sheet = stylesheet

        self.need_redraw(self.rect)

    style_sheet = property(__get_style_sheet, __set_style_sheet)

    def get_contents_child(self):
        return self

    def add_tag(self, tag):
        self.tags.add(tag)
        self.style = self.style_sheet.apply(self)
        self.need_redraw(self.rect)

    def remove_tag(self, tag):
        self.tags.discard(tag)
        self.style = self.style_sheet.apply(self)
        self.need_redraw(self.rect)

    def set_tag(self, tag, value):
        if value:
            self.add_tag(tag)
        else:
            self.remove_tag(tag)

    def __get_min_size(self):
        return self.__min_size or self.style.min_size or Vect(0, 0)

    def __set_min_size(self, value):
        if self.__min_size == value:
            return
        self.__min_size = value
        if self.parent:
            self.parent.need_compute_sizes()
            self.parent.need_organize()

    min_size = property(__get_min_size, __set_min_size)

    def __get_optimal_size(self):
        return self.__optimal_size or self.style.optimal_size or Vect(0, 0)

    def __set_optimal_size(self, value):
        assert isinstance(value, Vect)
        if value == self.__optimal_size:
            return
        self.__optimal_size = value
        if self.parent:
            self.parent.need_compute_sizes()
            self.parent.need_organize()

    optimal_size = property(__get_optimal_size, __set_optimal_size)

    def need_organize(self):
        self._need_organize = True
        self.set_messed()

    def need_compute_sizes(self):
        self._need_compute_sizes = True
        self.set_messed()

    def set_messed(self):
        self._messed = True
        if self.parent:
            self.parent.set_messed()

    def __get_size(self):
        return self.rect.size

    def __set_size(self, value):
        if value == self.size:
            return
        self.rect = Rect(self.rect.pos, value)
        self.need_organize()
    size = property(__get_size, __set_size)

    def __get_pos(self):
        return self.__pos

    def __set_pos(self, value):
        if value == self.__pos:
            return
        # XXX: ???
        self.need_redraw(self.rect)
        self.__pos = value
        self.need_redraw(self.rect)
    pos = property(__get_pos, __set_pos)

    def __get_contents_rect(self):
        return self.rect
    contents_rect = property(__get_contents_rect)

    def __get_contents_size(self):
        return self.contents_rect.size
    contents_size = property(__get_contents_size)

    def __get_contents_pos(self):
        return self.contents_rect.pos
    contents_pos = property(__get_contents_pos)

    def __get_screen(self):
        return self.parent.screen
    screen = property(__get_screen)

    def set_rect(self, r):
        self.rect = r
        # self.need_organize()

    def screen_pos(self):
        if self.screen is self.parent:
            return self.pos
        return self.pos + self.parent.screen_pos()

    def parent_as(self, cls):
        if not self.parent:
            return None
        if isinstance(self.parent, cls):
            return self.parent
        return self.parent.parent_as(cls)

    def focus_child(self, w):
        """Set the focus on a given child"""
        self.focused = w

    def add(self, w):
        """Add a child to the widget"""
        self.children.append(w)
        self.emit('add-child', w)   # XXX: remove ?
        self.need_compute_sizes()
        self.need_organize()

    def destroy(self):
        """destroy the widget

        Can be called several times on the same widget.
        """
        if self.__destroyed:
            return
        self.__destroyed = True
        if self.parent:
            self.parent.remove(self)
        self.emit('destroyed')
        for c in self.children[:]:
            c.destroy()

    def remove(self, w):
        """Remove a child from the widget"""
        self.children.remove(w)
        self.need_compute_sizes()
        self.need_organize()
        self.need_redraw(self.rect)

    def need_redraw(self, rect):
        self.surface = None
        if self.parent is not None:
            self.parent.need_redraw(rect.move(self.pos))

    def draw(self, painter):
        background = self.style.background
        if background:
            background.draw(painter, self.size)

    def _full_draw(self, painter):
        """Draw the widget on a painter object

        The position where we paint is stored in the painter itself
        (opengl style)
        """
        if self.store_surface and self.surface is None:
            surface = painter.surface_from_size(self.size)
            self.store_surface = False
            self._full_draw(painter.to_surface(surface))
            self.store_surface = True
            self.surface = surface

        if self.surface:
            painter.draw_surface(self.surface)
            return

        self.draw(painter)

        for c in self.children:
            painter.move(c.pos)
            mask = painter.mask
            painter.clip(c.rect)
            if painter.mask.intersect(c.rect):
                c._full_draw(painter)
            painter.mask = mask
            painter.umove(c.pos)

    def organize(self):
        """
        Set all children rect from the children min_size and optimal_size
        and from the widget size
        """
        rect = self.contents_rect
        for child in self.children:
            child.size = rect.size
            child.pos = rect.pos

    def _full_organize(self):
        assert not self._need_compute_sizes
        assert not self.parent or not self.parent._need_compute_sizes
        if not self._messed:
            return
        if self._need_organize:
            self.organize()
            self._need_organize = False
        self._messed = False
        for child in self.children:
            child._full_organize()
        self.need_redraw(self.rect)

    def compute_sizes(self):
        """
        compute the min_size and optimal_size of the widget from the
        children min_size and optimal size
        """
        if self.style.min_size:
            self.min_size = self.style.min_size
        else:
            self.min_size = Vect.merge(*(c.min_size for c in self.children))
        self.optimal_size = Vect.merge(
            self.min_size, *(c.optimal_size for c in self.children))

    def _full_compute_sizes(self):
        if not self._messed:
            return
        for child in self.children:
            child._full_compute_sizes()
        assert all(not x._need_compute_sizes for x in self.children)
        if self._need_compute_sizes:
            self.compute_sizes()
            self._need_compute_sizes = False

    def sorted_children(self):
        """Return the children, sorted with the one on top first"""
        # For the moment I suppose that none of the children overlap,
        # so we don't sort anything
        return self.children

    def mouse_down(self, pos):
        for c in self.sorted_children():
            cpos = pos - c.pos
            if not cpos in c.rect:
                continue
            if c.mouse_down(cpos):
                self.focused = c
                return True
        if self.clickable:
            self.emit('mouse-down', pos)
            return True
        return False

    def mouse_down_cancel(self):
        """Cancel the last mouse down event

        This function is mainly used for scrollable area, where we
        don't know from the beginning if we are clicking a widget, or
        just moving the area.
        """
        if self.focused:
            self.focused.mouse_down_cancel()
            self.focused = None

    def mouse_up(self, pos):
        if not self.focused:
            self.emit('mouse-up', pos)
            return True
        else:
            ret = self.focused.mouse_up(pos - self.focused.pos)
            self.focused = None
            return ret

    def mouse_motion(self, pos):
        if not self.focused:
            self.emit('mouse-motion', pos)
        else:
            self.focused.mouse_motion(pos - self.focused.pos)

    def key_down(self, key):
        for c in self.sorted_children():
            if c.key_down(key):
                return True
        return False
